import Redis from 'ioredis';
import { config } from '../config/env.js';
import { logger } from './logger.js';

class RedisClient {
  private static instance: RedisClient;
  private client: Redis.Redis;

  private constructor() {
    this.client = new Redis.default({
      host: config.redis.host,
      port: config.redis.port,
      password: config.redis.password || undefined,
      db: config.redis.db,
      retryStrategy: (times: number) => {
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
    });

    this.client.on('connect', () => {
      logger.info('Redis 连接成功');
    });

    this.client.on('error', (error: Error) => {
      logger.error('Redis 连接错误', { error });
    });
  }

  public static getInstance(): RedisClient {
    if (!RedisClient.instance) {
      RedisClient.instance = new RedisClient();
    }
    return RedisClient.instance;
  }

  /**
   * 设置键值对
   * @param key 键
   * @param value 值
   * @param expireSeconds 过期时间（秒）
   */
  public async set(key: string, value: string, expireSeconds?: number): Promise<void> {
    if (expireSeconds) {
      await this.client.set(key, value, 'EX', expireSeconds);
    } else {
      await this.client.set(key, value);
    }
  }

  /**
   * 获取键值
   * @param key 键
   */
  public async get(key: string): Promise<string | null> {
    return await this.client.get(key);
  }

  /**
   * 删除键
   * @param key 键
   */
  public async del(key: string): Promise<void> {
    await this.client.del(key);
  }

  /**
   * 设置过期时间
   * @param key 键
   * @param seconds 过期时间（秒）
   */
  public async expire(key: string, seconds: number): Promise<void> {
    await this.client.expire(key, seconds);
  }

  /**
   * 检查键是否存在
   * @param key 键
   */
  public async exists(key: string): Promise<boolean> {
    const result = await this.client.exists(key);
    return result === 1;
  }

  /**
   * 将指定键的值加1
   * @param key 键
   * @param expireSeconds 过期时间（秒），可选
   * @returns 自增后的值
   */
  public async incr(key: string, expireSeconds?: number): Promise<number> {
    if (expireSeconds) {
      // 使用 Lua 脚本确保操作的原子性
      const script = `
        local value = redis.call('incr', KEYS[1])
        redis.call('expire', KEYS[1], ARGV[1])
        return value
      `;
      return (await this.client.eval(script, 1, key, expireSeconds)) as number; // 类型断言为 number;
    }
    return await this.client.incr(key);
  }

  /**
   * 获取 Redis 客户端实例
   */
  public getClient(): Redis.Redis {
    return this.client;
  }

  /**
   * 获取分布式锁
   * @param lockKey 锁的键名
   * @param requestId 请求标识（用于解锁时验证）
   * @param expireSeconds 锁的过期时间（秒）
   * @param retryTimes 重试次数
   * @param retryDelay 重试延迟（毫秒）
   * @returns 是否获取成功
   */
  public async acquireLock(
    lockKey: string,
    requestId: string,
    expireSeconds: number = 30,
    retryTimes: number = 3,
    retryDelay: number = 100
  ): Promise<boolean> {
    const lockName = `lock:${lockKey}`;

    for (let i = 0; i <= retryTimes; i++) {
      // 使用 setnx 命令设置锁
      const result = await this.client.setnx(lockName, requestId);

      if (result === 1) {
        // 设置过期时间
        await this.client.expire(lockName, expireSeconds);
        return true;
      }

      if (i < retryTimes) {
        await new Promise(resolve => setTimeout(resolve, retryDelay));
      }
    }

    return false;
  }

  /**
   * 释放分布式锁
   * @param lockKey 锁的键名
   * @param requestId 请求标识
   * @returns 是否释放成功
   */
  public async releaseLock(lockKey: string, requestId: string): Promise<boolean> {
    const lockName = `lock:${lockKey}`;

    // 使用 Lua 脚本确保原子性操作
    const script = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;

    const result = await this.client.eval(script, 1, lockName, requestId);
    return result === 1;
  }
}

export const redis = RedisClient.getInstance();
